package mn.asri.teaproc.client;

/**
 * <b> IMPORTANT NOTE: TO KEEP GWT HAPPY AND JAVASCRIPT COMPATIBILITY, NO
 * JAVA5-STYLE CODING IS ALLOWED IN THIS FILE.  FURTHERMORE, CONSULT THE JAVA
 * CLASS SUPPORT WITHIN GWT (file:///${GWT_1_4_61_HOME}/doc/html/jre.html)
 * BEFORE MODIFYING THIS CODE. </b> </p>
 * <p/>
 * Client-side crypto to keep the browser-server communication a bit more
 * secured and less susceptible to prying eyes.  The crypto-algorithm used here
 * is based on the unicode-friendly TEA (or xxTEA).  Although it is not a
 * one-way encryption, it has been noted as a fast and secured algorithm.
 * <p/>
 * TEA requires the use of a key, which will be transmitted from the server-side
 * at the start of the client-server session.  Furthermore, different "module"
 * of the application will be transmitted with different crypto-key, for added
 * security (well, just a little).
 * <p/>
 * <b>NOTE: at some point, this class will be deprecated and be used strictly
 * with the client package.  This package is JUST a POC. :-) </b>
 *
 * @author <a href="mailto:mike.liu@aptechmongolia.edu.mn">Mike Liu</a>
 * @version $Id: Crypto.java 5 2008-09-01 12:08:42Z mikeliucc $
 */
public final class Crypto {

	/** Encrypt data with key. */
	public static byte[] encrypt(byte[] data, byte[] key) {
		if (data.length == 0) { return data; }
		int[] dataInts = toIntArray(data, true);
		int[] keyInts = toIntArray(key, false);
		return toByteArray(encrypt(dataInts, keyInts), false);
	}

	/** Decrypt data with key. */
	public static byte[] decrypt(byte[] data, byte[] key) {
		if (data.length == 0) { return data; }
		int[] dataInts = toIntArray(data, false);
		int[] keyInts = toIntArray(key, false);
		return toByteArray(decrypt(dataInts, keyInts), true);
	}

	/** Encrypt data with key. */
	public static int[] encrypt(int[] v, int[] k) {
		int n = v.length - 1;
		if (n < 1) { return v; }
		if (k.length < 4) {
			int[] key = new int[4];
			for (int i = 0; i < k.length; i++) { key[i] = k[i]; }
			k = key;
		}

		int z = v[n], y = v[0], delta = 0x9E3779B9, sum = 0, e;
		int p, q = 6 + 52 / (n + 1);
		while (q-- > 0) {
			sum = sum + delta;
			e = sum >>> 2 & 3;
			for (p = 0; p < n; p++) {
				y = v[p + 1];
				z = v[p] +=
					(z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^
					(sum ^ y) + (k[p & 3 ^ e] ^ z);
			}
			y = v[0];
			z = v[n] +=
				(z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^
				(sum ^ y) + (k[p & 3 ^ e] ^ z);
		}
		return v;
	}

	/** Decrypt data with key. */
	public static int[] decrypt(int[] v, int[] k) {
		int n = v.length - 1;
		if (n < 1) { return v; }
		if (k.length < 4) {
			int[] key = new int[4];
			for (int i = 0; i < k.length; i++) { key[i] = k[i]; }
			k = key;
		}
		int z = v[n], y = v[0], delta = 0x9E3779B9, sum, e;
		int p, q = 6 + 52 / (n + 1);
		sum = q * delta;
		while (sum != 0) {
			e = sum >>> 2 & 3;
			for (p = n; p > 0; p--) {
				z = v[p - 1];
				y = v[p] -=
					(z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^
					(sum ^ y) + (k[p & 3 ^ e] ^ z);
			}
			z = v[n];
			y = v[0] -=
				(z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^
				(sum ^ y) + (k[p & 3 ^ e] ^ z);
			sum = sum - delta;
		}
		return v;
	}

	/** Convert byte array to int array. */
	private static int[] toIntArray(byte[] data, boolean includeLength) {
		int n = (((data.length & 3) == 0) ?
		         (data.length >>> 2) :
		         ((data.length >>> 2) + 1));
		int[] result;
		if (includeLength) {
			result = new int[n + 1];
			result[n] = data.length;
		} else {
			result = new int[n];
		}
		n = data.length;
		for (int i = 0; i < n; i++) {
			result[i >>> 2] |= (0x000000ff & data[i]) << ((i & 3) << 3);
		}
		return result;
	}

	/** Convert int array to byte array. */
	private static byte[] toByteArray(int[] data, boolean includeLength) {
		int n = data.length << 2;
		if (includeLength) {
			int m = data[data.length - 1];
			if (m > n) { return null; } else { n = m; }
		}
		byte[] result = new byte[n];
		for (int i = 0; i < n; i++) {
			result[i] = (byte) ((data[i >>> 2] >>> ((i & 3) << 3)) & 0xff);
		}
		return result;
	}

}